package gbench.appdemo.crawler.junit;

import static gbench.common.tree.LittleTree.IRecord.REC;

import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.index.Term;
import org.junit.jupiter.api.Test;
import gbench.appdemo.crawler.analyzer.YuhuanAnalyzer;
import gbench.appdemo.crawler.analyzer.lexer.ILexProcessor;
import gbench.appdemo.crawler.analyzer.lexer.Lexeme;
import gbench.appdemo.crawler.analyzer.lexer.Trie;
import gbench.appdemo.crawler.srch.AbstractJdbcSrchEngine;
import gbench.common.fs.FileSystem;
import gbench.common.tree.LittleTree.IRecord;
import static gbench.appdemo.crawler.srch.SrchUtils.*;

/**
 * 简单的搜索引擎
 * @author gbench
 *
 */
public class JunitSrch {
    
    public class FileSrchEngine extends AbstractJdbcSrchEngine {
        
        /**
         * corpusPath 语料库路径
         * @return
         */
        public YuhuanAnalyzer getYunHuan(final String corpusDir) {
            final var trie = new Trie<String>();// 语料库的根节点树
            final var corpusHome = new File(corpusDir);
            
            // 构建语料库
            ( ( corpusHome.isDirectory() )
                ? Stream.of(corpusHome.listFiles())
                : Stream.of(new File(FileSystem.path(corpusDir, this.getClass())))
            ).filter(file->file.getAbsolutePath().endsWith(".txt")).forEach(file->{// 创建语料文件
                FileSystem.utf8lines(file)
                .filter(line->!line.matches("^\\s*$")) // 去除空行
                .forEach(e->{// 提取语料词汇
                    Stream.of(e.strip().split("[\n]+"))
                    .forEach(keyword->{// 设置词素 的节点属性信息
                        keywords.add(keyword);
                        final var points = keyword.split("");// 把keyword 切分乘字符点
                        Trie.addPoints2Trie(trie, points)
                        .addAttribute("category","word")// 绑定词法类型信息
                        .addAttribute("meaning", Arrays.stream(points).collect(Collectors.joining()));
                    });
                });// forEach
            });
            
            // 打印语料库
            //Trie.traverse(trie, e->System.out.println("\t".repeat(e.getLevel())+e.getValue()+"\t type:"+e.getAttribute("type")));
            
            @SuppressWarnings("resource")
            final var yuhuan = new YuhuanAnalyzer().addProcessor(new ILexProcessor() { // 创建一个 分词器
                
                /**
                 * 把符号转换成 词素
                 * @param symbol 符号
                 * @return 词素
                 */
                @Override 
                public Lexeme evaluate(String symbol) {
                    final var cur_trie = trie.getTrie(symbol.split(""));// 提取节点词素
                    if(cur_trie==null)return null;
                    final var category = cur_trie.getAttribute("category")+"";
                    return new Lexeme(symbol,category,"unknown");
                }
                
                @Override 
                public boolean isPrefix( final String line ) {
                    if( Stream.of(// pattern 列表
                        "[a-zA-Z]+", // 英文单词
                        "[a-zA-Z_-]+", // 英文标识符号
                        "[\\d\\.]+", // 阿拉伯数据
                        "[一二三四五六七八九十零百千万亿兆]+",// 中文数字
                        "[一二三四五六七八九十零百千万亿兆\\d]+年([一二三四五六七八九十零百千万亿兆\\d+](月([一二三四五六七八九十零百千万亿兆\\d+]日?)?)?)?") // 日期前缀的判断
                    .anyMatch(line::matches) ) return true;
                    
                    return trie.isPrefix(line.split(""));
                }// isPrefix
            });// addProcessor
            
            return yuhuan;
        }

        /**
         * 搜索引擎初始化
         */
        public void initialize() {
            analyzer = this.getYunHuan("C:\\Users\\gbench\\Desktop\\中国笔记\\逻辑词汇");
        }
        
        /**
         * 
         * @param analyzer
         */
        public void setAnalyzer(final Analyzer analyzer) {
            this.analyzer = analyzer;
        }
        
        /**
         * 
         * @param analyzer
         * @param fieldName
         * @param line
         * @throws Exception
         */
        public List<IRecord> splits(final Analyzer analyzer, final String fieldName, final String text) {
            final var ll = new LinkedList<IRecord>();
            try {
                // 获得tokenStream对象
                // 第一个参数：域名，可以随便给一个
                // 第二个参数：要分析的文本内容
                final var tokenStream = analyzer.tokenStream(fieldName, text);
                // 添加一个引用，可以获得每个关键词
                final var charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class);
                // 添加一个偏移量的引用，记录了关键词的开始位置以及结束位置
                final var offsetAttribute = tokenStream.addAttribute(OffsetAttribute.class);
                // 将指针调整到列表的头部
                tokenStream.reset();

                // 遍历关键词列表，通过incrementToken方法判断列表是否结束
                while (tokenStream.incrementToken()) {
                    final var rec = REC("start", offsetAttribute.startOffset(), "end", offsetAttribute.endOffset(), "value",
                            charTermAttribute.toString());
                    rec.add("type",rec.str("value").matches("[~!@#$%^&*\\(\\)\\s\\.。:;]+")?"symbol":"word");// 加入符号类型的
                    ll.add(rec);
                }
                tokenStream.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            analyzer.close();
            return ll;
        }
        
        /**
         * 创建索引文件
         * 
         * @param recs 文件记录集合流：symbol 关键词，statement 上下文语句, file:文件名称
         */
        public synchronized void indexFiles(final Stream<IRecord> recs) {
         // 索引书写器的配置
            final var iwc = new IndexWriterConfig(analyzer).setOpenMode(OpenMode.CREATE_OR_APPEND);
            
            // 使用try 块自动关闭 writer 
            try(final var writer = new IndexWriter(this.getIndexHome(), iwc);) {
                final var proto = REC();// 基础结构
                recs.parallel().forEach(rec->{// 并行处理
                    //System.out.println(rec);
                    try {
                        final var symbol = rec.get("symbol");// 检索词
                        final var statement = rec.get("statement");// 语句上下文
                        final var file = rec.get("file");// 文件名称
                        final var id = md5(rec);// md5 的去重标记
                        final var doc = rec2doc(proto.derive(// 定义文档结构
                            "id?",id, // 临时字段用户同一批次的数据的 去重
                            "symbol",symbol, // 文法符号
                            "search_field",symbol, // 检索符号
                            "text",statement,// 上下文本
                            "file",file
                        ));// doc
                        writer.updateDocument(new Term("id",id),doc);// 文档的去重保存
                    } catch (IOException e) {
                        e.printStackTrace();
                    }// try
                });// parallelStream 分词处理
                writer.forceMerge(1);
                writer.commit();
            } catch (Exception e) {
                e.printStackTrace();
            }// try
        }
        
        /**
         * 获取关键词集合
         * @return 关键词集合
         */
        public Set<String> getKeywords() {
            return keywords;
        }
        
        private Set<String> keywords = new HashSet<String>();// 关键词集合
    }
    
    /**
     * 文件遍历
     * 
     * @param file 文件路径
     * @param cs 文件处理函数
     */
    public static void traverse(final File file, final Consumer<File> cs) {
        if (file == null || !file.exists())
            return;
        if (file.isFile())
            cs.accept(file);
        else if (file.isDirectory())
            Arrays.stream(file.listFiles()).parallel().forEach(f -> traverse(f, cs));
    }
    
    /**
     * 切分文本信息:line成上下文 句子
     * @param start:开始位置
     * @param end：结束位置
     * @param line 文本信息
     * @return 包含有 start 的一段文本语句
     */
    public static String snippet(final int start, final int end, final String line) {
        
        final var range = new Integer[] {0,line.length()};
        final String pattern = "。!;“”：，";// 分句的标志性标点
        Stream.of(-start,end).forEach(pos->{// 检索最近的
            for(final var c :pattern.toCharArray()) {// 提取语句段落
                boolean flag = false;// 是否切分语句完成
                for(int i= (pos<0? start:end) ;( pos<=0? i>=0 : i<line.length() ); i+= pos<=0?-1:1){
                    if(line.charAt(i)==c) {
                        range[pos>0?1:0]=i;
                        flag = true;
                        break;
                    }//if
                }//for
                if(flag)break;
            }//for
        });
        
        return line.substring(range[0],range[1]+1<line.length()?range[1]+1:range[1]) ;// 文本截取
    }
    
    /**
     * 文件检索
     */
    @Test
    public void searchFiles() {
        final var srchEngine = new FileSrchEngine();
        srchEngine.initialize();
        System.out.println(srchEngine.getIndexHome());
        srchEngine.lookup("但是", 50).forEach(rec->{
            System.out.println(rec);
        });
    }
    
    @Test
    public void indexFiles() {
        final var srchEngine = new FileSrchEngine();
        final var yuhuan = srchEngine.getYunHuan("C:\\Users\\gbench\\Desktop\\中国笔记\\逻辑词汇");
        srchEngine.setAnalyzer(yuhuan);
        final var recs = new ConcurrentLinkedQueue<IRecord>(); // 生成一个并发队列，用于接收各个 文档的处理信息。
        // 检索每个文件并生成索引
        traverse(new File("C:\\Users\\gbench\\Desktop\\中国笔记\\逻辑散文"), file->{
            if(file.getName().endsWith("txt")) // 仅对文本文件进行处理
            FileSystem.utf8lines(file).forEach(line->{
                yuhuan.analyze(line).stream()
                .filter(e->e.get("category").equals("word"))
                .forEach(rec->{
                    final var statement = snippet( rec.i4("start"),  rec.i4("end"), line);// 文本片段
                    // 语句
                    recs.add(REC(// 加入字段分析结果
                        "symbol",rec.get("symbol"),
                        "statement",statement,
                        "file",file.getName()
                    ));// add
                    System.out.println(
                        MessageFormat.format("\n token\t{0}:\n eg:{1}\n file:{2}", 
                        rec,
                        statement,
                        file.getName()
                    ));
                });// forEach
            });// forEach
            
            // 创建索引文件
            srchEngine.indexFiles(recs.stream());
        });
        
    }

}
